#include "HerzeleidPrecompiled.hxx"
#include "HerzeleidBasicAudioEngine.hxx"

using namespace HerzeleidMediaNamespaceImpl;

AudioEngineCallbacks::AudioEngineCallbacks()
{
	// Do nothing for now 
}

Void HERZELEID_METHODSTDCALLTYPE AudioEngineCallbacks::OnProcessingPassStart()
{
	// Do nothing for now 
}

Void HERZELEID_METHODSTDCALLTYPE AudioEngineCallbacks::OnProcessingPassEnd()
{
	// Do nothing for now 
}

Void AudioEngineCallbacks::Initialize( _In_ AudioEngineBasePtr HostPtr )
{
	m_HostPtr = HostPtr;
}

Void HERZELEID_METHODSTDCALLTYPE AudioEngineCallbacks::OnCriticalError( _In_ HResult Error )
{
	m_HostPtr->SetExperiencedCriticalError();
}

Void AudioEngineBase::SetExperiencedCriticalError()
{
	m_bEngineExperiencedCriticalError = true;
}

Bool AudioEngineBase::HasExperiencedCriticalError()
{
	return( m_bEngineExperiencedCriticalError );
}

// Reverb parameters 

HERZELEID_NOALIAS Void AudioEngineBase::GetDefaultReverbConfigurationForSmallRoom( 
	_In_ AudioEffectReverbDescPtr pReverbDesc
	)
{
	if ( !pReverbDesc ) return;
    pReverbDesc->ReflectionsDelay = XAUDIO2FX_REVERB_DEFAULT_REFLECTIONS_DELAY;
    pReverbDesc->ReverbDelay = XAUDIO2FX_REVERB_DEFAULT_REVERB_DELAY;
    pReverbDesc->RearDelay = XAUDIO2FX_REVERB_DEFAULT_REAR_DELAY;
    pReverbDesc->PositionLeft = XAUDIO2FX_REVERB_DEFAULT_POSITION;
    pReverbDesc->PositionRight = XAUDIO2FX_REVERB_DEFAULT_POSITION;
    pReverbDesc->PositionMatrixLeft = XAUDIO2FX_REVERB_DEFAULT_POSITION_MATRIX;
    pReverbDesc->PositionMatrixRight = XAUDIO2FX_REVERB_DEFAULT_POSITION_MATRIX;
    pReverbDesc->EarlyDiffusion = 4;
    pReverbDesc->LateDiffusion = 15;
    pReverbDesc->LowEQGain = XAUDIO2FX_REVERB_DEFAULT_LOW_EQ_GAIN;
    pReverbDesc->LowEQCutoff = XAUDIO2FX_REVERB_DEFAULT_LOW_EQ_CUTOFF;
    pReverbDesc->HighEQGain = XAUDIO2FX_REVERB_DEFAULT_HIGH_EQ_GAIN;
    pReverbDesc->HighEQCutoff = XAUDIO2FX_REVERB_DEFAULT_HIGH_EQ_CUTOFF;
    pReverbDesc->RoomFilterFreq = XAUDIO2FX_REVERB_DEFAULT_ROOM_FILTER_FREQ;
    pReverbDesc->RoomFilterMain = XAUDIO2FX_REVERB_DEFAULT_ROOM_FILTER_MAIN;
    pReverbDesc->RoomFilterHF = XAUDIO2FX_REVERB_DEFAULT_ROOM_FILTER_HF;
    pReverbDesc->ReflectionsGain = XAUDIO2FX_REVERB_DEFAULT_REFLECTIONS_GAIN;
    pReverbDesc->ReverbGain = XAUDIO2FX_REVERB_DEFAULT_REVERB_GAIN;
    pReverbDesc->DecayTime = XAUDIO2FX_REVERB_DEFAULT_DECAY_TIME;
    pReverbDesc->Density = XAUDIO2FX_REVERB_DEFAULT_DENSITY;
    pReverbDesc->RoomSize = XAUDIO2FX_REVERB_DEFAULT_ROOM_SIZE;
    pReverbDesc->WetDryMix = XAUDIO2FX_REVERB_DEFAULT_WET_DRY_MIX;
    pReverbDesc->DisableLateField = TRUE;
}

HERZELEID_NOALIAS Void AudioEngineBase::GetDefaultReverbConfigurationForLargeRoom( 
	_In_ AudioEffectReverbDescPtr pReverbDesc
	)
{
	if ( !pReverbDesc ) return;
    pReverbDesc->ReflectionsDelay = XAUDIO2FX_REVERB_DEFAULT_REFLECTIONS_DELAY;
    pReverbDesc->ReverbDelay = XAUDIO2FX_REVERB_DEFAULT_REVERB_DELAY;
    pReverbDesc->RearDelay = XAUDIO2FX_REVERB_DEFAULT_REAR_DELAY;
    pReverbDesc->PositionLeft = XAUDIO2FX_REVERB_DEFAULT_POSITION;
    pReverbDesc->PositionRight = XAUDIO2FX_REVERB_DEFAULT_POSITION;
    pReverbDesc->PositionMatrixLeft = XAUDIO2FX_REVERB_DEFAULT_POSITION_MATRIX;
    pReverbDesc->PositionMatrixRight = XAUDIO2FX_REVERB_DEFAULT_POSITION_MATRIX;
    pReverbDesc->EarlyDiffusion = 4;
    pReverbDesc->LateDiffusion = 4;
    pReverbDesc->LowEQGain = XAUDIO2FX_REVERB_DEFAULT_LOW_EQ_GAIN;
    pReverbDesc->LowEQCutoff = XAUDIO2FX_REVERB_DEFAULT_LOW_EQ_CUTOFF;
    pReverbDesc->HighEQGain = XAUDIO2FX_REVERB_DEFAULT_HIGH_EQ_GAIN;
    pReverbDesc->HighEQCutoff = XAUDIO2FX_REVERB_DEFAULT_HIGH_EQ_CUTOFF;
    pReverbDesc->RoomFilterFreq = XAUDIO2FX_REVERB_DEFAULT_ROOM_FILTER_FREQ;
    pReverbDesc->RoomFilterMain = XAUDIO2FX_REVERB_DEFAULT_ROOM_FILTER_MAIN;
    pReverbDesc->RoomFilterHF = XAUDIO2FX_REVERB_DEFAULT_ROOM_FILTER_HF;
    pReverbDesc->ReflectionsGain = XAUDIO2FX_REVERB_DEFAULT_REFLECTIONS_GAIN;
    pReverbDesc->ReverbGain = XAUDIO2FX_REVERB_DEFAULT_REVERB_GAIN;
    pReverbDesc->DecayTime = XAUDIO2FX_REVERB_DEFAULT_DECAY_TIME;
    pReverbDesc->Density = 10;
    pReverbDesc->RoomSize = XAUDIO2FX_REVERB_DEFAULT_DENSITY;
    pReverbDesc->WetDryMix = XAUDIO2FX_REVERB_DEFAULT_WET_DRY_MIX;
    pReverbDesc->DisableLateField = FALSE;
}

// Audio engine constructore and destructor 

AudioEngineBase::AudioEngineBase(  ) : 
	m_bEngineInitialized( false )
{
	DirectX::ThrowIfFailed( 
		MFStartup( MF_VERSION ) 
		);
}

AudioEngineBase::~AudioEngineBase()
{
	SuspendAudio();
	MFShutdown();
}

Void AudioEngineBase::SuspendAudio()
{
	if ( m_bEngineExperiencedCriticalError )
	{
		return;
	}

	if ( m_bAudioStarted )
	{
		if ( m_MediaEngine )
		{
			m_MediaEngine->Pause();
		}

		m_SoundEffectEngine->StopEngine();
	}

	m_bAudioStarted = false;
}

Void AudioEngineBase::ResumeAudio()
{
	if ( m_bEngineExperiencedCriticalError )
	{
		return;
	}

	if ( FAILED(m_SoundEffectEngine->StartEngine()) )
	{
		m_bEngineExperiencedCriticalError = true;
	}
}

Void AudioEngineBase::SetSoundEffectVolume( 
	BasicSoundEffectPtr pEffect, 
	Float32 fVolume 
	)
{
	if ( pEffect )
	{
		pEffect->SoundEffectSourceVoice->SetVolume(
			fVolume
			);
	}
}

Void AudioEngineBase::StartSoundEffect( 
	BasicSoundEffectPtr pEffect
	)
{
	if ( m_bEngineExperiencedCriticalError )
	{
		return;
	}

	if (FAILED( pEffect->SoundEffectSourceVoice->Start() ))
	{
		SetExperiencedCriticalError();
		return;
	}

	{
		XAUDIO2_VOICE_STATE State = { 0 };
		pEffect->SoundEffectSourceVoice->GetState( 
			&State, XAUDIO2_VOICE_NOSAMPLESPLAYED
			);

		if ( !State.BuffersQueued )
		{
			pEffect->SoundEffectSourceVoice->SubmitSourceBuffer(
				&pEffect->SoundEffectBufferDesc
				);
		}
	}

	pEffect->SoundEffectStarted = true;
}

Void AudioEngineBase::StopSoundEffect( 
	BasicSoundEffectPtr pEffect
	)
{
	if ( m_bEngineExperiencedCriticalError )
	{
		return;
	}

	if (FAILED( pEffect->SoundEffectSourceVoice->Stop() ))
	{
		SetExperiencedCriticalError();
		return;
	}

	pEffect->SoundEffectStarted = false;
}

Void AudioEngineBase::Start()
{
	if ( m_bEngineExperiencedCriticalError )
	{
		return;
	}

	if ( m_MediaEngine )
	{
		// Ignore result.
		AutoDebugVariable(Result) m_MediaEngine->Play();
	}

	m_bAudioStarted = true;
}

Bool AudioEngineBase::HasSoundEffectStarted( 
	BasicSoundEffectPtr pEffect
	)
{
	return( pEffect->SoundEffectStarted );
}

Void AudioEngineBase::SetSoundEffectPitch( 
	BasicSoundEffectPtr pEffect, 
	Float32 fPitch 
	)
{
	if ( pEffect )
	{
		pEffect->SoundEffectSourceVoice->SetFrequencyRatio(
			fPitch
			);
	}
}

Void AudioEngineBase::SetSoundEffectFilter( 
	BasicSoundEffectPtr pEffect, 
	Float32 fFrequency, 
	Float32 fOneOverQ 
	)
{
	if ( pEffect )
	{
		if ( fOneOverQ <= 0.1f )
		{
			fOneOverQ = 0.1f;
		}

		if ( fOneOverQ > XAUDIO2_MAX_FILTER_ONEOVERQ )
		{
			fOneOverQ = XAUDIO2_MAX_FILTER_ONEOVERQ;
		}

		XAUDIO2_FILTER_PARAMETERS Params;
		Params.Frequency = XAudio2CutoffFrequencyToRadians( fFrequency, pEffect->SoundEffectSampleRate );
		Params.OneOverQ = fOneOverQ;
		Params.Type = LowPassFilter;

		pEffect->SoundEffectSourceVoice->SetFilterParameters(
			&Params
			);
	}
}

Void AudioEngineBase::SetRoomSize( 
	Float32 fRoomSize, 
	Float32Ptr pfWallDistances 
	)
{
	// Maximum "outdoors" is a room size of 1000.0f
    float fNormalizedRoomSize = fRoomSize  / 1000.0f;

    float faOutputMatrix[4] = {0, 0, 0, 0};
    float fLeftRatio = pfWallDistances[2] / (pfWallDistances[2] + pfWallDistances[3]);

    if ((pfWallDistances[2] + pfWallDistances[3]) == 0)
    {
        fLeftRatio = 0.5f;
    }

    float fWidth = abs(
			(pfWallDistances[3] + pfWallDistances[5] + pfWallDistances[7]) / 3.0f - 
			(pfWallDistances[2] + pfWallDistances[4] + pfWallDistances[6]) / 3.0f
		);

    float fHeight = abs(pfWallDistances[1] - pfWallDistances[0]);
    float fWidthNormalized = fWidth / MaxOf( fWidth, fHeight );

    if ( fHeight == 0 )
    {
        fWidthNormalized = 1.0f;
    }

    // As fWidthNormalized approaches 0, the room is taller than wide.
    // This means that the late-field reverb should be non-localized across both speakers.
    fLeftRatio -= .5f;
    fLeftRatio *= fWidthNormalized;
    fLeftRatio += .5f;

    // Near-field reverb with the speaker closest to the wall getting more reverb
    faOutputMatrix[0] = (1.0f - fLeftRatio)* fNormalizedRoomSize;
    faOutputMatrix[1] = 0;
    faOutputMatrix[2] = 0;
    faOutputMatrix[3] = fLeftRatio * fNormalizedRoomSize;
    DirectX::ThrowIfFailed(
		m_SoundEffectReverbVoiceSmallRoom->SetOutputMatrix(
			m_SoundEffectMasteringVoice, 2, 2, 
			faOutputMatrix
			)
        );

    // Late-field reverb: The wall furthest away gets more of the late-field reverb
    faOutputMatrix[0] = fLeftRatio * (1.0f - fNormalizedRoomSize);
    faOutputMatrix[1] = 0;
    faOutputMatrix[2] = 0;
    faOutputMatrix[3]= (1.0f - fLeftRatio) * (1.0f - fNormalizedRoomSize);
    DirectX::ThrowIfFailed(
        m_SoundEffectReverbVoiceLargeRoom->SetOutputMatrix(
			m_SoundEffectMasteringVoice, 2, 2, 
			faOutputMatrix
			)
        );
}








